#ifndef SHADER_H
#define SHADER_H

#include "engine.h"
#include <utils.h>

namespace engine
{
    class Shader
    {
    public:
        unsigned int ID;

        Shader(const char *vertexPath, const char *fragmentPath)
        {
            string mVertexShaderString;
            string mFragmentShaderString;
            if (!UTILS::ReadAllText(string(vertexPath), mVertexShaderString))
            {
                ENGINE_ERROR("read vertext shader failed");
                return;
            }
            if (!UTILS::ReadAllText(string(fragmentPath), mFragmentShaderString))
            {
                ENGINE_ERROR("read fragment shader failed");
                return;
            }
            const char *vShaderCode = mVertexShaderString.c_str();
            const char *fShaderCode = mFragmentShaderString.c_str();
            // 2. compile shaders
            unsigned int vertex, fragment;
            // vertex shader
            vertex = glCreateShader(GL_VERTEX_SHADER);
            glShaderSource(vertex, 1, &vShaderCode, NULL);
            glCompileShader(vertex);
            checkCompileErrors(vertex, "VERTEX");
            // fragment Shader
            fragment = glCreateShader(GL_FRAGMENT_SHADER);
            glShaderSource(fragment, 1, &fShaderCode, NULL);
            glCompileShader(fragment);
            checkCompileErrors(fragment, "FRAGMENT");
            // shader Program
            ID = glCreateProgram();
            glAttachShader(ID, vertex);
            glAttachShader(ID, fragment);
            glLinkProgram(ID);
            checkCompileErrors(ID, "PROGRAM");
            // delete the shaders as they're linked into our program now and no longer necessary
            glDeleteShader(vertex);
            glDeleteShader(fragment);
            ENGINE_DEBUG("load shader - file: ({},{}) ,id: {}\n", vertexPath, fragmentPath, ID);
        }
        // activate the shader
        // ------------------------------------------------------------------------
        void use()
        {
            glUseProgram(ID);
        }
        // utility uniform functions
        // ------------------------------------------------------------------------
        void setBool(const std::string &name, bool value) const
        {
            glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
        }
        // ------------------------------------------------------------------------
        void setInt(const std::string &name, int value) const
        {
            glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
        }
        // ------------------------------------------------------------------------
        void setFloat(const char *name, float value) const
        {
            glUniform1f(glGetUniformLocation(ID, name), value);
        }

        void setMatrix4(const char *name, const glm::mat4 matrix)
        {
            glUniformMatrix4fv(glGetUniformLocation(this->ID, name), 1, false, glm::value_ptr(matrix));
        }

        void setVector3f(const char *name, const glm::vec3 vec)
        {
            glUniform3f(glGetUniformLocation(this->ID, name), vec.x, vec.y, vec.z);
        }

    private:
        // utility function for checking shader compilation/linking errors.
        // ------------------------------------------------------------------------
        void checkCompileErrors(unsigned int shader, std::string type)
        {
            int success;
            char infoLog[1024];
            if (type != "PROGRAM")
            {
                glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
                if (!success)
                {
                    glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                    ENGINE_ERROR("ERROR::SHADER_COMPILATION_ERROR of type: %s\n%s\n", type.c_str(), infoLog);
                }
            }
            else
            {
                glGetProgramiv(shader, GL_LINK_STATUS, &success);
                if (!success)
                {
                    glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                    ENGINE_ERROR("ERROR::PROGRAM_LINKING_ERROR of type: %s\n%s\n", type.c_str(), infoLog);
                }
            }
        }
    };

}

#endif